package server

import (
	"errors"
	imapp "go.manyji.com/im3/app"
	"go.manyji.com/im3/app/auth"
	"go.manyji.com/im3/crypto"
	"go.manyji.com/lib/app"
	"go.manyji.com/lib/config"
	"go.manyji.com/lib/json"
	"go.manyji.com/lib/log"
	"go.manyji.com/lib/util/rand"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

var (
	fSessExpire = config.Int("session.expire", 3600, "session过期时间 秒")
	fCookieName = config.String("session.cookie", "session", "session cookie name")
	sessions    = map[int]*session{}
	sesslock    = sync.Mutex{}
)

type session struct {
	Id         int
	Uid        int
	SessionKey string
	ExpireTime int64
}

func init() {
	AfterDbOpen(func() {
		if err := loadSessions(); err != nil {
			log.Fatal(err)
		}
		app.Async(func() {
			for {
				time.Sleep(time.Second)
				delExpireSession()
			}
		})
	})
}

func loadSessions() error {
	rows, err := db.Query("SELECT `id`,`Uid`,`SessionKey`,`ExpireTime` FROM `app_session`")
	if err != nil {
		return err
	}
	defer rows.Close()
	sesslock.Lock()
	defer sesslock.Unlock()
	sessions = map[int]*session{}
	for rows.Next() {
		sess := &session{}
		if err := rows.Scan(&sess.Id, &sess.Uid, &sess.SessionKey, &sess.ExpireTime); err != nil {
			return err
		}
		sessions[sess.Uid] = sess
	}
	return nil
}

func delExpireSession(args ...interface{}) {
	sesslock.Lock()
	defer sesslock.Unlock()
	now := time.Now().Unix()
	del := []*session{}
	for _, sess := range sessions {
		if now > sess.ExpireTime {
			del = append(del, sess)
		}
	}

	if len(args) > 0 {
		if _, ok := sessions[args[0].(int)]; ok {
			del = append(del, &session{
				Uid: args[0].(int),
			})
		}
	} else {
		for _, sess := range sessions {
			if now > sess.ExpireTime {
				del = append(del, sess)
			}
		}
	}

	for _, sess := range del {
		delete(sessions, sess.Uid)
		_, _ = db.Exec("DELETE FROM `app_session` WHERE `id`=?", sess.Id)
	}
}

func GetSessionKey(uid int, args ...interface{}) string {

	return getSessionKey(uid, args...)
}

func getSessionKey(uid int, args ...interface{}) string {
	sesslock.Lock()
	defer sesslock.Unlock()
	sess, ok := sessions[uid]
	if !ok {
		return ""
	}

	if len(args) == 1 && args[0].(bool) == false {
		return sess.SessionKey
	}

	sess.ExpireTime = time.Now().Unix() + int64(*fSessExpire)
	db.Exec("UPDATE `app_session` SET `ExpireTime`=? WHERE `id`=?", sess.ExpireTime, sess.Id)

	return sess.SessionKey
}

func setSessionKey(uid int, skey string) error {
	sesslock.Lock()
	defer sesslock.Unlock()
	et := time.Now().Unix() + int64(*fSessExpire)
	if sess, ok := sessions[uid]; ok {
		if _, err := db.Exec("UPDATE `app_session` SET `SessionKey`=?,`ExpireTime`=? WHERE `id`=?", skey, et, sess.Id); err != nil {
			return err
		}
		sess.SessionKey = skey
		sess.ExpireTime = et
	} else {
		res, err := db.Exec("INSERT INTO `app_session` (`Uid`,`SessionKey`,`ExpireTime`) VALUES (?,?,?)", uid, skey, et)
		if err != nil {
			return err
		}
		id, _ := res.LastInsertId()
		sessions[uid] = &session{
			Id:         int(id),
			Uid:        uid,
			SessionKey: skey,
			ExpireTime: et,
		}
	}
	return nil
}

type loginToken struct {
	Uid int
}

func getLoginToken(r *http.Request) *loginToken {
	token := ""
	if ck, err := r.Cookie("app" + strconv.Itoa(imapp.AppId()) + "_" + *fCookieName); err == nil {
		token = ck.Value
	}
	if *fDebugMode {
		if token == "" {
			token = r.PostForm.Get("_t")
		}
		if token == "" {
			token = r.URL.Query().Get("_t")
		}
	}
	if token == "" {
		return nil
	}
	ss := strings.SplitN(token, "-", 2)
	uid, err := strconv.Atoi(ss[0])
	if err != nil {
		return nil
	}
	if *fDebugMode && *fDebugSession != "" && ss[1] == *fDebugSession {
		return &loginToken{Uid: uid}
	}
	skey := getSessionKey(uid)
	if skey == "" {
		return nil
	}
	b, err := crypto.AesDecrypt(skey, ss[1])
	if err != nil {
		return nil
	}
	tk := &loginToken{}
	if err := json.Unmarshal(b, &tk); err != nil {
		return nil
	}
	if uid != tk.Uid {
		return nil
	}
	return tk
}

func GetLoginUid(r *http.Request) int {
	tk := getLoginToken(r)
	if tk == nil {
		return 0
	}
	return tk.Uid
}
func generateToken(uid int) (string, error) {
	tk := &loginToken{Uid: uid}
	b, err := json.Marshal(tk)
	if err != nil {
		return "", err
	}
	skey := rand.RandStr(32)
	if err := setSessionKey(uid, skey); err != nil {
		return "", err
	}
	str, err := crypto.AesEncrypt(skey, b)
	return str, err
}
func GenerateToken(uid int) string {
	if *fDebugMode {
		str, _ := generateToken(uid)
		return str
	}
	return ""
}
func createLoginToken(uid int, w http.ResponseWriter) error {
	str, err := generateToken(uid)
	if err != nil {
		return err
	}
	http.SetCookie(w, &http.Cookie{
		Name:     "app" + strconv.Itoa(imapp.AppId()) + "_" + *fCookieName,
		Value:    strconv.Itoa(uid) + "-" + str,
		HttpOnly: false,
		Path:     *fPathRoot,
		Secure:   *fTlsCert != "" && *fTlsKey != "",
	})
	return nil
}

func HandleLogin(w http.ResponseWriter, r *http.Request, f func()) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	if err := func() error {
		args := r.URL.Query()
		suid := args.Get("uid")
		uid, err := strconv.Atoi(suid)
		if err != nil {
			return err
		}
		sign := args.Get("sign")
		if !auth.VerifyAuth(suid, sign) {
			return errors.New("密钥验证失败")
		}

		if !isAdmin(uid) {
			//u, ok := deptUserMap[uid]
			//if !ok {
			//	return errors.New("该用户没有权限")
			//}
			//
			//if perms, ok := rolePerm[u.Title]; !ok || len(perms) == 0 {
			//	return errors.New("该用户没有权限")
			//}

			_, ok := deptUserMap[uid]
			if !ok {
				return errors.New("该用户没有权限")
			}

			//if perms, ok := rolePerm[u.Title]; !ok || len(perms) == 0 {
			//	return errors.New("该用户没有权限")
			//}
		}
		// IP, _, _ := net.SplitHostPort(r.RemoteAddr)
		// db.InsertOperation(uid, "loginIn", "login", r.Header.Get("User-Agent"), IP)
		if err := createLoginToken(uid, w); err != nil {
			return errors.New("内部错误")
		}
		f()
		http.Redirect(w, r, *fPathRoot, http.StatusFound)
		return nil
	}(); err != nil {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>错误提示页面</title><style>.point_text{color:#ff4c41;text-align:center;margin:200px 0 0 0;font-style:normal;font-weight:400}.back_btn{text-align:center;margin:30px 0 0 0}.back_btn a{text-decoration:none;font-size:16px;padding:6px 20px;border:none;background:#00c2b2;color:#fff;border-radius:10px;cursor:pointer;outline:0}.back_btn a:hover{opacity:.8}</style></head><body><h3 class="point_text">` + err.Error() + `</h3><div class="back_btn"><a href="` + auth.AuthPageUrl() + `">确定</a></div></body></html>`))
	}
}

func HandleLogout(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")

	http.SetCookie(w, &http.Cookie{
		Name:   "app" + strconv.Itoa(imapp.AppId()) + "_" + *fCookieName,
		MaxAge: -1,
		Path:   *fPathRoot,
	})

	//delExpireSession(GetLoginUid(r))
	auth.ShowAuthPage(w, r)
}
